其他
The House of Mind
本文为看雪论坛精华文章
看雪论坛作者ID:jmpcall
一
"The House of Mind"的学习条件
另外,本文是对phrack杂志中一篇神作的总结和补充,所以exploit code和更完整的分析过程,请阅读原文:http://phrack.org/issues/66/10.html。
二
什么是"The House of Mind"?
再具体一点就是,在应用程序分配到的内存周围,很多都是glibc内部使用的内存,程序存在漏洞,攻击者就有机会通过构造输入数据,溢出glibc内部使用的变量,进一步控制malloc()/free()的执行逻辑,最终借glibc之手,修改某个函数对应的got表项(可以理解为函数指针,感兴趣也可以看看我的另外一个帖子:https://bbs.pediy.com/thread-246373.htm),使其指向一段shell code(同样通过用户输入构造)。
三
漏洞程序
/*
* K-sPecial's vulnerable program
*/
#include <stdio.h>
#include <stdlib.h>
int main (void) {
char *ptr = malloc(1024); /* First allocated chunk */
char *ptr2; /* Second chunk */
/* ptr & ~(HEAP_MAX_SIZE-1) = 0x08000000 */
int heap = (int)ptr & 0xFFF00000;
_Bool found = 0;
printf("ptr found at %p\n", ptr); /* Print address of first chunk */
// i == 2 because this is my second chunk to allocate
for (int i = 2; i < 1024; i++) {
/* Allocate chunks up to 0x08100000 */
if (!found && (((int)(ptr2 = malloc(1024)) & 0xFFF00000) == \
(heap + 0x100000))) {
printf("good heap allignment found on malloc() %i (%p)\n", i, ptr2);
found = 1; /* Go out */
break;
}
}
malloc(1024); /* Request another chunk: (ptr2 != av->top) */
/* Incorrect input: 1048576 bytes */
fread (ptr, 1024 * 1024, 1, stdin);
free(ptr); /* Free first chunk */
free(ptr2); /* The House of Mind */
return(0); /* Bye */
}
① ptr = malloc(1024);
heap = (int)ptr & 0xFFF00000; // 将ptr值按1M向下取整
② 循环执行ptr2 = malloc(1024),直到(ptr2 & 0xFFF00000) == (heap + 0x100000)
③ 再次执行一次malloc(1024)
④ fread(ptr, 1024 * 1024, 1, stdin);
使用fead()函数读取用户输入,是为了减小攻击难度,如果换成strcpy()函数,读到'\0'字符就不会再读了,解决这个问题,要使用的就是另外的技术了,作者为了让大家专注于"The House of Mind",就特地避开了更复杂的情况。
⑤ free(ptr);
free(ptr2);
四
攻击过程分析
最低3位清0为0x408,表示chunk总大小为1032字节,最低3位二进制值为101,表示A=1、M=0、P=1,这是要欺骗glibc认为chunk2在thread arena中(chunk2实际在main arena中)。
#define heap_for_ptr(ptr) \
((heap_info *)((unsigned long)(ptr) & ~(HEAP_MAX_SIZE-1)))
#define arena_for_chunk(ptr) \
(chunk_non_main_arena(ptr) ? heap_for_ptr(ptr)->ar_ptr : &main_arena)
由于ar_ptr为heap_info结构的第一个成员,所以按照布局图中的构造地址,glibc会认为arena管理结构位于0x804a014。
根据布局图可以看出,fake_arena开始的8个字节,都被构造为0x102,剩余部分全部构造为DTORS_END-12,根据malloc_state结构的定义可知,这样构造肯定可以使fake_arena->bins[2] = DTORS_END-12。
struct malloc_state {
/* Serialize access. */
mutex_t mutex;
// Should we have padding to move the mutex to its own cache line?
#if THREAD_STATS
/* Statistics for locking. Only used if THREAD_STATS is defined. */
long stat_lock_direct, stat_lock_loop, stat_lock_wait;
#endif
/* The maximum chunk size to be eligible for fastbin */
INTERNAL_SIZE_T max_fast; /* low 2 bits used as flags */
/* Fastbins */
mfastbinptr fastbins[NFASTBINS];
/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;
/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;
/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2];
/* Bitmap of bins */
unsigned int binmap[BINMAPSIZE];
/* Linked list */
struct malloc_state *next;
/* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
} else
clear_inuse_bit_at_offset(nextchunk, 0);
/*
Place the chunk in unsorted chunk list. Chunks are
not placed into regular bins until after they have
been given one chance to be used in malloc.
*/
bck = unsorted_chunks(av); // 返回:&fake_arena->bins[0]
fwd = bck->fd; // fd位于malloc_chunk结构体8字节偏移处
// 所以fwd = bck->fd = fake_arena->bins[2] = DTORS_END-12
p->bk = bck;
p->fd = fwd;
bck->fd = p; // fake_arena->bins[2] = p
fwd->bk = p; // bk位于malloc_chunk结构体12字节偏移处
// 所以这里会将p,写到DTORS_END指向的内存单元,即:got[.dtors] = p
set_head(p, size | PREV_INUSE);
set_foot(p, size);
check_free_chunk(av, p);
}
在(3)中已经一起解释了,是为了使got[.dtors] = p,代码中的p,对应的是布局图中的chunk2,所以等到.dtors()函数执行时,实际上是执行chunk2位置的"nop; nop; jmp 0x0c"(nop机器码为0x90,jmp 0x0c机器码为0xeb0c,所以共4字节)。
向前跳转0x0c偏移,是因为接紧着的4个字节,用于存放0x40d,再往后的8字节,会被上述代码中的p->bk=bck和p->fd=fwd两条赋值语句覆盖,所以shell code一定要构造在chunk2->bk之后的位置。
0x804a014也就是在(2)中,为fake_heap->ar_ptr构造的值,作者一开始是将fake_arena构造在0x804a010位置的,还特地在0x804a010位置构造了一个0,作为fake_arena->mutex值,但是他通过调试发现,漏洞程序在执行完free(ptr)后,会将0x804a014位置清0,这样,为fake_arena->max_fast构造的0x102(为了使判断2、判断5不成立),就被覆盖了,从而使后续的攻击逻辑执行失败。作者说被覆盖的原因,可能是由于_int_free()结束之后,执行mutex_unlock()导致的:
我认为不是这个原因,通过布局图可以看出,构造数据并没有修改chunk->size,所以free(ptr)就是按照正常的glibc逻辑执行的,那么mutex_unlock()修改的一定是main_arena全局全量中的mutex,而不可能是这里,一些其它版本的glibc中,0x804a010、0x804a014位置分别会是fd_nextsize、bk_nextsize。
在(6)中已经说过了,free(ptr)结束后,会将0x804a014位置清0,所以正好可以作为fake_arena->mutex值,0x804a018位置的0x102,作为fake_arena->max_fast值。
漏洞程序退出循环后,又调用了一次malloc(),这也是为了满足"The House of Mind"的利用条件(使判断10不成立)。
五
攻击可行性证明
/*
* p > -size, 即:p > 0-size,即:p+size > 0,在p、size都大于0的前提下,表示p+size溢出了,但是由于p和size都是无符号数,p+size >= 0恒成立,所以写成p > -size
* 个人感觉更严谨应该是:p >= -size,因为p+size == 0,也表示溢出了(比如size=0x00000001,则p=-size=0xffffffff(取反+1)时,p+size==0也为溢出)
*/
if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
|| __builtin_expect ((uintptr_t) p & MALLOC_ALIGN_MASK, 0))
if ((unsigned long)(size) <= (unsigned long)(av->max_fast)
#if TRIM_FASTBINS
/*
If TRIM_FASTBINS set, don't place chunks
bordering top into fastbins
*/
&& (chunk_at_offset(p, size) != av->top)
#endif
)
else if (!chunk_is_mmapped(p))
if (__builtin_expect (p == av->top, 0))
if (__builtin_expect (contiguous (av)
&& (char *) nextchunk
>= ((char *) av->top + chunksize(av->top)), 0))
if (__builtin_expect (!prev_inuse(nextchunk), 0))
if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (nextsize >= av->system_mem, 0))
if (!prev_inuse(p))
if (nextchunk != av->top)
if (!nextinuse)
六
glibc改造
不能影响正常逻辑
不能添加一个判断后,正常的逻辑也不对了。
比如业务层调用free(),glibc会将释放chunk放在相应的缓存链表中,而判断是否double free,只会拿当前释放chunk和链表头中的第一个chunk,进行地址对比,而不会遍历整个链表对比。
看雪ID:jmpcall
https://bbs.pediy.com/user-home-815036.htm
# 往期推荐
6.Windows本地提权漏洞CVE-2014-1767分析及EXP编写指导
球分享
球点赞
球在看
点击“阅读原文”,了解更多!